import configparser
import ctypes
import os
import sys
import time
# from logging.handlers import RotatingFileHandler  # 不再需要，loguru内置轮转功能
from multiprocessing import parent_process

import psutil
from PyQt6.QtNetwork import QTcpSocket
from PyQt6.QtCore import QProcess, QThreadPool, QMutex
import json
from typing import List  # 导入List用于类型注解
import platform
import uuid
import wmi
from colorama import Fore, Style, init

from NetManager.GameTx import GameTx
from 验证.VerifyUtil import VerifyUtil
from loguru import logger
G_VER = "20251024.01"

ConfigFileName = "config.ini"  # 配置文件名称
is_running = True  # 全局变量，用于控制线程运行状态
G_EnableLog=False #全局是否启用日志
G_Config = {}  # 用于保存cfg的配置json
mainWindow = None  # 用于存放main窗口实例
configWindow=None   #配置窗口
G_GlobalConfig={}
G_Dbc={"phone":{}}   #资源数据

G_ScriptRunTime= 0

G_YZ:VerifyUtil=VerifyUtil()

# 配置主程序日志器G_Log，输出到app.log
# 确保Log目录存在
os.makedirs("Log", exist_ok=True)

# 创建主程序专用的logger
G_Log = logger.bind(subsystem="Global")
# 为G_Log添加app.log文件输出，使用独立的handler
G_Log.add(
    "Log/app.log",
    rotation="5 MB",
    retention=5,
    encoding='utf-8',
    format="{time:YYYY-MM-DD HH:mm:ss} - {extra[subsystem]} - {level} - {message}",
    level="DEBUG",
    enqueue=True,
    filter=lambda record: record["extra"].get("subsystem") == "Global"  # 只记录主程序日志
)


#init(autoreset=True)
# ColoredConsoleFormatter类已移除，loguru内置颜色支持
def create_file_logger(name, log_file, max_bytes=5 * 1024 * 1024, backup_count=5):
    """
    创建支持大小限制的独立日志器

    Args:
        name: 日志器名称
        log_file: 日志文件路径
        max_bytes: 单个日志文件最大字节数（默认5MB）
        backup_count: 保留的旧日志文件数量（默认5个）
    """
    from loguru import logger as base_logger
    
    # 确保日志目录存在
    abs_log_path = os.path.abspath(log_file)
    log_dir = os.path.dirname(abs_log_path)
    os.makedirs(log_dir, exist_ok=True)
    
    # 创建一个独立的logger实例，使用bind方法创建上下文
    file_logger = base_logger.bind(window_name=name)
    
    # 为这个logger添加专用的文件处理器
    handler_id = file_logger.add(
        abs_log_path,
        rotation=f"{max_bytes // (1024*1024)} MB",  # 按大小轮转，转换为MB
        retention=backup_count,  # 保留文件数量
        encoding='utf-8',
        format="{time:YYYY-MM-DD HH:mm:ss} - {extra[window_name]} - {level} - {message}",
        level="DEBUG",
        enqueue=True,  # 启用异步写入，避免阻塞
        filter=lambda record: record["extra"].get("window_name") == name  # 只记录属于这个窗口的日志
    )
    
    # 将handler_id和名称存储到logger对象中
    file_logger.handler_id = handler_id
    file_logger.logger_name = name
    
    return file_logger
class GameDataStruct:
    def __init__(self):
        self.data = Player.copy()  # 默认初始化为Player的副本
        #self.socket = QTcpSocket()  # 套接字
        self.process = None  # 进程信息
        self.handle=0#进程句柄
        self.isAutoScriptRun = False  # AutoScript是否启动
        self.isScriptRun = False  # 是否脚本运行中
        self.isInit = False  # 初始化安装环境
        #self.timeStamp = 0  # 启动时间戳
        #self.heartStamp = 0 #验证心跳时间
        #self.yz :VerifyUtil =None #网络验证
        #self.mulatorInfo:MulatorInfoStruct ={}  #初始化模拟器信息
        self.proxy=None
        self.Log=None  #日志 (loguru logger)
        self.LogHandle=0
        self.GateWay:GameTx=None #网关通讯
        self.Game:GameTx = None  #游戏通讯
        self.version=""#版本
        self.Info={}
        self.param =None
        self.Result={}
        self.isInBattle =False #是否在战斗
        self.isRepetition_Battle =False #是否在重复战斗
        self.isCustomAccount=False #是否疑似客户号
        self.PublicEvent =None #公用事件
        self.GameFun =None
        self.game_ip = "" #游戏ip
        self.game_port =0 #游戏端口

class MulatorInfoStruct:
    def __init__(self):
        self.deviceName=""  #设备名称,mumu为ip加端口号
        self.headless_pid=0 #虚拟机进程
        self.index =-1 #模拟器索引
        self.is_android_started= False #是否进入安卓
        self.render_wnd=0 #用于图色的窗口句柄
# 全局变量
Player = {
    "user": "",  # 账号
    "pass": "",  # 密码
    "jobId": "",  # 职业ID
    "jobName": "",  # 职业名称
    "areaId": 0,  # 大区ID
    "areaName": "",  # 大区名称
    "serverId": 0,  # 服务器ID
    "serverName": "",  # 服务器名称
    "config": "",  # 配置
    "roleidx": 0,  # 选择角色序号
    "roleName": "",  # 角色名称
    "mapName": "",  # 地图名称
    "level": 0,  # 等级
    "viplevel": 0,  # VIP等级
    "gold": 0,  # 金币
    "diamond": 0,  # 钻石
    "bindYB": 0,  # 绑定元宝
    "money": 0,
    "money2":0,
    "status": "脱机",  # 运行状态
    "notes": "",  # 备注
    "ip": "",  # IP
    "port":0, #端口
    "appVer": "",  # APP版本
    "agentVer": "",  # 代理版本
    "dllVer": "",  # dll版本
    "processID": 0,  # 进程ID
    "proxyType": "",  # 代理类型  http  https socket5
    "proxyIP": "",  # 代理IP
    "proxyPort": 0,  # 代理端口
    "proxyUser": "",  # 代理账号
    "proxyPass": "",  # 代理密码
    "cardUser":"",#单窗口的验证,也可以用作用户验证的账号
    "cardPass":'',#预留用户密码用作用户验证
    "cardExp":'',#验证到期时间
    "getwayIP":"",#网关IP
    "getwayPort":"",#网关端口
    "user_id":"",#角色ID
    "accountPath":"", #账号配置文件路径
    "chapter":0,#章节
    "floor":0,#关卡
    "power":0,#体力
    "produceAccount":0,#生产账号数量
    "produceUploadCount":0,#生产上传自选
    "email":"",#邮箱
    "email_pass":"",#邮箱密码
    "token":"", #令牌


}

G_Game: List[GameDataStruct] = []  # 全局变量，明确类型为GameDataStruct的数组
G_ProcessPool = QThreadPool()  # 全局线程池
# G_BindGame :GameDataStruct= GameDataStruct()
# G_GameInfo:GameDataStruct = GameDataStruct()
@staticmethod
def check_process_exists(pid):
    try:
        c = wmi.WMI()
        for process in c.Win32_Process():
            if process.ProcessId == pid:
                return True
        return False
    except:
        return False


def LOGD(sort, data=None) -> None:
    try:
        if data is None:
            # 单参数模式: LOGD("message")
            data = sort  # 此时 sort 实际是 data 的值
            if parent_process() is None:
                G_Log.info(data)
            # else:
            #     G_GameInfo.data["notes"] = data
            #     if G_EnableLog and G_GameInfo.Log:
            #         G_GameInfo.Log.info(data)
        else:
            if sort > -1 and sort < len(G_Game):
                G_Game[sort].data["notes"] = data
                if G_EnableLog:
                    # 如果Log为None，创建一个logger
                    if G_Game[sort].Log is None:
                        G_Game[sort].Log = create_file_logger(f"窗口{sort}", f"Log/窗口{sort}.log")
                    G_Game[sort].Log.info(data)
            else:
                if parent_process() is None:
                    if G_EnableLog:
                        G_Log.info(data)
                # else:
                #     G_GameInfo.data["notes"] = data
                #     if G_EnableLog and G_GameInfo.Log:
                #         G_GameInfo.Log.info(data)
    except Exception as e:
        G_Log.error(f"LOGD异常:{e}")


def LOGE(sort, data=None) -> None:
    try:
       if data is None:
           data = sort  # 此时 sort 实际是 data 的值
           if parent_process() is None:
               # 单参数模式: LOGE("message")
               #print(f"日志_Error->内容:{data}\n")
               G_Log.error(data)
           # else:
           #     G_GameInfo.data["notes"] = data
           #     print(f"日志_Error->内容:{data}\n")
           #     if G_EnableLog and G_GameInfo.Log:
           #         G_GameInfo.Log.error(data)
       else:
           if sort > -1 and sort < len(G_Game):
               G_Game[sort].data["notes"] = data
               if G_EnableLog:
                   # 如果Log为None，创建一个logger
                   if G_Game[sort].Log is None:
                       G_Game[sort].Log = create_file_logger(f"窗口{sort}", f"Log/窗口{sort}.log")
                   G_Game[sort].Log.error(data)
    except Exception as e:
        G_Log.error(f"LOGE异常:{e}")

@staticmethod
def GetMachineCode():
    try:
        # 获取当前操作系统的版本信息
        versionString = platform.version()

        # 获取硬盘序列号
        HD_ID = "00000000"  # 默认值

        c = wmi.WMI()
        for disk in c.Win32_DiskDrive():
            if disk.SerialNumber:
                HD_ID = disk.SerialNumber.strip()
                break
 
        ret = versionString + "|" + HD_ID
    except Exception as e:
        LOGE(f"GetMachineCode error: {str(e)}")
        ret = "unknown"  # 异常情况返回默认值
    return ret[:64]

@staticmethod
def KillProcess(process_id):
    try:
        handle = ctypes.windll.kernel32.OpenProcess(0x0001, False, process_id)
        if handle:
            result = ctypes.windll.kernel32.TerminateProcess(handle, 0)
            ctypes.windll.kernel32.CloseHandle(handle)
            return result != 0
        return False
    except Exception as e:
        LOGE(f"终止进程失败: {e}")
        return False

@staticmethod
def KillProcessByHandle(handle):
    try:
        if handle:
            result = ctypes.windll.kernel32.TerminateProcess(handle, 0)
            ctypes.windll.kernel32.CloseHandle(handle)
            return result != 0
        return False
    except Exception as e:
        LOGE(f"终止进程失败: {e}")
        return False
@staticmethod
def IsProcessRunning(process_id):
    try:
        return psutil.pid_exists(process_id)
    except:
        return False

@staticmethod
def GetTimeStamp(is_10len=False):
    """获取当前时间戳

    Args:
        is_10len (bool): 是否返回10位秒级时间戳，默认True。
                         False时返回13位毫秒级时间戳

    Returns:
        int: 时间戳整数
    """
    if is_10len:
        return int(time.time())
    else:
        return int(time.time() * 1000)


# @staticmethod
# def read_ini(file_path, section, key, default_value=""):
#     """
#     读取 .ini 文件中的字段值。
#
#     参数:
#         file_path (str): .ini 文件的相对路径。
#         section (str): 节点名称。
#         key (str): 字段名称。
#         default_value: 如果字段不存在时的默认值，可以是字符串、数值或布尔值。
#
#     返回:
#         根据default_value的类型返回对应类型的值：字符串、数值或布尔值。
#     """
#     # 根据默认值类型使用对应的ConfigParser方法获取值
#     try:
#         # 将相对路径转换为绝对路径
#         abs_file_path = os.path.abspath(file_path)
#         # 创建 ConfigParser 对象
#         config = configparser.ConfigParser()
#         # 确定默认值的类型
#         value_type = type(default_value)
#
#         # 如果文件不存在，则创建文件并写入默认值
#         if not os.path.exists(abs_file_path):
#             config[section] = {key: str(default_value)}
#             with open(abs_file_path, 'w') as configfile:
#                 config.write(configfile)
#             return default_value
#
#         # 读取文件
#         config.read(abs_file_path)
#
#         # 如果节点或字段不存在，则创建并写入默认值
#         if section not in config:
#             config[section] = {key: str(default_value)}
#             with open(abs_file_path, 'w') as configfile:
#                 config.write(configfile)
#             return default_value
#
#         if key not in config[section]:
#             config[section][key] = str(default_value)
#             with open(abs_file_path, 'w') as configfile:
#                 config.write(configfile)
#             return default_value
#         if value_type == bool:
#             return config[section].getboolean(key)
#         elif value_type == int:
#             return config[section].getint(key)
#         elif value_type == float:
#             return config[section].getfloat(key)
#         else:
#             # 字符串或其他类型，直接返回
#             return config[section][key]
#     except (ValueError, configparser.NoOptionError):
#         # 如果转换失败，返回默认值
#         return default_value
#
# @staticmethod
# def write_ini(file_path, section, key, value):
#     """
#     写入 .ini 文件中的字段值。
#
#     参数:
#         file_path (str): .ini 文件的相对路径。
#         section (str): 节点名称。
#         key (str): 字段名称。
#         value: 要写入的值，可以是字符串、数值或布尔值。
#     """
#
#
#     # 写回文件，使用UTF-8编码，确保中文不会被转换为Unicode
#     try:
#         # 将相对路径转换为绝对路径
#         abs_file_path = os.path.abspath(file_path)
#
#         # 创建 ConfigParser 对象
#         config = configparser.ConfigParser()
#
#         # 如果文件存在，则读取文件
#         if os.path.exists(abs_file_path):
#             config.read(abs_file_path)
#
#         # 如果节点不存在，则创建节点
#         if section not in config:
#             config[section] = {}
#
#         # 根据值的类型进行适当的转换
#         # 布尔值、整数和浮点数都需要转换为字符串
#         # ConfigParser在写入时只接受字符串类型
#         if isinstance(value, bool):
#             # 布尔值转换为'True'或'False'字符串
#             config[section][key] = str(value)
#         elif isinstance(value, (int, float)):
#             # 数值转换为字符串
#             config[section][key] = str(value)
#         else:
#             # 字符串或其他类型，直接写入
#             config[section][key] = str(value)
#         with open(abs_file_path, 'w') as configfile:
#             config.write(configfile)
#     except Exception as e:
#         LOGE(f"写入配置文件异常：路径={abs_file_path} 错误={str(e)}")

@staticmethod
def read_ini(file_path, section, key, default_value=""):
    """
    读取 .ini 文件中的字段值。

    参数:
        file_path (str): .ini 文件的相对路径。
        section (str): 节点名称。
        key (str): 字段名称。
        default_value: 如果字段不存在时的默认值，可以是字符串、数值或布尔值。

    返回:
        根据default_value的类型返回对应类型的值：字符串、数值或布尔值。
    """
    # 根据默认值类型使用对应的ConfigParser方法获取值
    try:
        # 将相对路径转换为绝对路径
        abs_file_path = os.path.abspath(file_path)
        # 创建 ConfigParser 对象
        config = configparser.ConfigParser()
        # 确定默认值的类型
        value_type = type(default_value)

        # 如果文件不存在，则创建文件并写入默认值
        if not os.path.exists(abs_file_path):
            return default_value

        # 读取文件
        config.read(abs_file_path,encoding='utf-8')
        if not config.has_section(section):
            return default_value
        if not config.has_option(section, key):
            return default_value

        if value_type == bool:
            return config.getboolean(section,key,fallback=default_value)
        elif value_type == int:
            return config.getint(section, key, fallback=default_value)
        elif value_type == float:
            return config.getfloat(section, key, fallback=default_value)
        else:
            # 字符串或其他类型，直接返回
            return config.get(section, key, fallback=default_value)
    except (ValueError, configparser.NoOptionError):
        # 如果转换失败，返回默认值
        return default_value

@staticmethod
def write_ini(file_path, section, key, value):
    """
    写入 .ini 文件中的字段值。

    参数:
        file_path (str): .ini 文件的相对路径。
        section (str): 节点名称。
        key (str): 字段名称。
        value: 要写入的值，可以是字符串、数值或布尔值。
    """


    # 写回文件，使用UTF-8编码，确保中文不会被转换为Unicode
    try:
        # 将相对路径转换为绝对路径
        abs_file_path = os.path.abspath(file_path)
        os.makedirs(os.path.dirname(abs_file_path), exist_ok=True)
        # 创建 ConfigParser 对象
        config = configparser.ConfigParser()
        # 如果文件存在，则读取文件
        if os.path.exists(abs_file_path):
            config.read(abs_file_path,encoding='utf-8')
        # 如果节点不存在，则创建节点
        if config.has_section(section)==False:
            config.add_section(section)

        # 根据值的类型进行适当的转换
        # 布尔值、整数和浮点数都需要转换为字符串
        # ConfigParser在写入时只接受字符串类型
        if isinstance(value, bool):
            # 布尔值转换为'True'或'False'字符串
            config.set(section,key,str(value))
        elif isinstance(value, (int, float)):
            # 数值转换为字符串
            config.set(section, key, str(value))
        else:
            # 字符串或其他类型，直接写入
            config.set(section, key, str(value))
        with open(abs_file_path, 'w',encoding='utf-8') as configfile:
            config.write(configfile)
    except Exception as e:
        LOGE(f"写入配置文件异常：路径={abs_file_path} 错误={str(e)}")
import random
import string
@staticmethod
def 取随机字符串(min_length, max_length,  use_lowercase=True,use_uppercase=False, use_digits=False, first_char_not_digit=True,force_mixed=False):
    """
       生成随机字符串。

       参数:
           min_length (int): 字符串的最小长度。
           max_length (int): 字符串的最大长度。
           use_uppercase (bool): 是否启用大写字母。
           use_lowercase (bool): 是否启用小写字母。
           use_digits (bool): 是否启用数字。
           first_char_not_digit (bool): 第一个字符是否不能是数字。
           force_mixed (bool): 是否强制混合所有启用的字符类型（确保每种类型至少出现一次）。

       返回:
           str: 生成的随机字符串。
       """
    # 定义字符池
    char_pool = ''
    if use_uppercase:
        char_pool += string.ascii_uppercase
    if use_lowercase:
        char_pool += string.ascii_lowercase
    if use_digits:
        char_pool += string.digits

    # 检查字符池是否为空
    if not char_pool:
        raise ValueError("至少需要启用一种字符类型（大写字母、小写字母或数字）。")

    # 生成随机长度
    length = random.randint(min_length, max_length)

    # 生成随机字符串
    random_string = []
    for i in range(length):
        if i == 0 and first_char_not_digit:
            # 第一个字符不能是数字
            char = random.choice([c for c in char_pool if not c.isdigit()])
        else:
            char = random.choice(char_pool)
        random_string.append(char)
    result = ''.join(random_string)
    # 如果不强制混合，直接返回
    if force_mixed:
        # 检查是否包含所有启用的字符类型
        checks_passed = True
        if use_uppercase and not any(c.isupper() for c in result):
            checks_passed = False
        if use_lowercase and not any(c.islower() for c in result):
            checks_passed = False
        if use_digits and not any(c.isdigit() for c in result):
            checks_passed = False

        # 如果检查通过，返回结果；否则继续循环生成
        if checks_passed:
            return result
    return result
@staticmethod
def generate_random_ip():
    return ".".join(str(random.randint(0, 255)) for _ in range(4))